#pragma once

#ifndef _CORE_QUESTION_AND_ANSWER_H_
#define _CORE_QUESTION_AND_ANSWER_H_

#include <string>
#include <vector>
#include "Poco\SharedPtr.h"
#include "EncodedFile.h"

namespace ExamSystem
{
    using Poco::SharedPtr;

    enum QuestionType
    {
        typeChoice = 0,
        typeFilling,
        typeTyping,
        typeQnA,
        typeOffice,
        typeCoding,
        typeHtml,
        typePhotoshop,
        QuestionTypeCount
    };

    extern std::string gQuestionTypeNames[QuestionTypeCount];
    extern std::wstring gQuestionTypeChineseNames[QuestionTypeCount];

    //////////////////////////////////////////////////////////////////////////
    // base classes
    struct QuestionBase
    {
        QuestionBase(void) : questionID(-1) {; }
        QuestionBase(int id, const std::string &title) : questionID(id), questionTitle(title) {; }

        int          questionID;
        std::string  questionTitle;
    };

    class CQuestion : public QuestionBase
    {
    public:
        virtual ~CQuestion(void) { ; }

    public:
        // question type and id
        // the question id should be unique in a type
        virtual QuestionType Type(void) const = 0;
    };

    class CCriterion
    {
    protected:
        CCriterion(void) : questionID(-1) { ; }
    public:
        virtual ~CCriterion(void) { ; }

    public:
        // question type and id
        // they should be coincident with the question
        virtual QuestionType Type(void) const = 0;
        int questionID;
    };

    struct AnswerBase
    {
        AnswerBase(void) : questionID(-1), answerScore(0) { ; }
        AnswerBase(int id, double score) : questionID(id), answerScore(score) { ; }
        int questionID;
        double answerScore;
    };

    class CAnswer : public AnswerBase
    {
    public:
        virtual ~CAnswer(void) { ; }

    public:
        // question type and id
        // they should be coincident with the question
        virtual QuestionType Type(void) const = 0;
        virtual void Score(CCriterion *criterion) { answerScore = 0.0; }
    };
    //////////////////////////////////////////////////////////////////////////

    // casts
    template<typename T, typename K>
    inline T &StaticCast(SharedPtr<K> &ptr)
    {
        return *static_cast<T *>(ptr.get());
    }
    template<typename T, typename K>
    inline const T &StaticCast(const SharedPtr<K> &ptr)
    {
        return *static_cast<const T *>(ptr.get());
    }
    template<typename T, typename K>
    inline T &DynamicCast(SharedPtr<K> &ptr)
    {
        return *dynamic_cast<T *>(ptr.get());
    }
    template<typename T, typename K>
    inline const T &DynamicCast(const SharedPtr<K> &ptr)
    {
        return *dynamic_cast<const T *>(ptr.get());
    }

    //////////////////////////////////////////////////////////////////////////
    // choice question
    class CChoiceQuestion : public CQuestion
    {
    public:
        virtual QuestionType Type(void) const { return typeChoice; }        
        std::vector<std::string> questionOptions;
    };

    class CChoiceCriterion : public CCriterion
    {
    public:
        CChoiceCriterion(void) : criterionChoice(-1) { ; }
        virtual QuestionType Type(void) const { return typeChoice; }
        int criterionChoice;
    };

    class CChoiceAnswer : public CAnswer
    {
    public:
        CChoiceAnswer(void) : answerChoice(-1) { ; }
        virtual QuestionType Type(void) const { return typeChoice; }
        virtual void Score(CCriterion *criterion) 
        {
            if (static_cast<CChoiceCriterion *>(criterion)->criterionChoice == answerChoice)
                answerScore = 1.0;
            else 
                answerScore = 0.0;
        }

        int answerChoice;
    };

    //////////////////////////////////////////////////////////////////////////
    // fill-in-the-blank question
    class CFillingQuestion : public CQuestion
    {
    public:
        virtual QuestionType Type(void) const { return typeFilling; }
    };

    class CFillingCriterion : public CCriterion
    {
    public:
        virtual QuestionType Type(void) const { return typeFilling; }
        std::vector<std::string> critierionBlanks;
    };

    class CFillingAnswer : public CAnswer
    {
    public:
        virtual QuestionType Type(void) const { return typeFilling; }
        virtual void Score(CCriterion *criterion) 
        {
            CFillingCriterion &c = *static_cast<CFillingCriterion *>(criterion);
            if (c.critierionBlanks.size() != answerBlanks.size())
                answerScore = 0.0;
            else
            {
                // we score the filling questions by blanks
                answerScore = 0.0;
                for (size_t i = 0; i < answerBlanks.size(); i++)
                    if (c.critierionBlanks[i] == answerBlanks[i])
                        answerScore += 1.0;
            }
        }

        std::vector<std::string> answerBlanks;
    };

    //////////////////////////////////////////////////////////////////////////
    // typing question
    class CTypingQuestion : public CQuestion
    {
    public:
        CTypingQuestion(void) : questionTime(60), flagChineseTyping(0) { ; }
        virtual QuestionType Type(void) const { return typeTyping; }
        unsigned int flagChineseTyping;
        unsigned int questionTime;
    };

    class CTypingCriterion : public CCriterion
    {
    public:
        virtual QuestionType Type(void) const { return typeTyping; }
        std::string criterionTyping;
    };

    class CTypingAnswer : public CAnswer
    {
    public:
        virtual QuestionType Type(void) const { return typeTyping; }
        virtual void Score(CCriterion *criterion) 
        {
            CTypingCriterion &c = *static_cast<CTypingCriterion *>(criterion);
            if (answerTyping == c.criterionTyping)
                answerScore = 1.0;
            else 
                answerScore = 0.0;
        }

        std::string answerTyping;
    };  
    //////////////////////////////////////////////////////////////////////////
    // QNA question
    class CQNAQuestion : public CQuestion
    {
    public:
        virtual QuestionType Type(void) const { return typeQnA; }
    };

    class CQNACriterion : public CCriterion
    {
    public:
        virtual QuestionType Type(void) const { return typeQnA; }

        std::string criterionText;
    };

    class CQNAAnswer : public CAnswer
    {
    public:
        virtual QuestionType Type(void) const { return typeQnA; }
        std::string answerText;
    };

    ////////////////////////////////////////////////////////////////////
    // these are prototype of questions, criterion, answers including file
    class CFiledQuestion : public CQuestion
    {
    public:
        CFiledQuestion(void) : questionStartFileIdx(-1) {}

        CEncodedFile questionFile;
        int questionStartFileIdx;
        std::vector<std::string> answerFiles;
    };

    class CFiledCriterion : public CCriterion
    {
    public:
        CEncodedFile criterionFile;
    };

    class CFiledAnswer : public CAnswer
    {
    public:
        CEncodedFile answerFile;
    };

    /////////////////////////////////////////////////////////////
    // criterion with type
    class CPropertyCriterion : public CCriterion
    {
    public:
        std::vector<std::string> criterionProperties;
        std::vector<std::string> criterionValues;
        std::vector<double> criterionWeights;
    };

    class CPropertyAnswer : public CFiledAnswer
    {
    public:
        std::vector<std::string> answerProperties;
        std::vector<std::string> answerValues;

        std::string answerParser;
        virtual void Score(CCriterion *criterion);

    protected:
        void PrepareScoring(void);
    };


    //////////////////////////////////////////////////////////////////////////
    // office question
    class COfficeQuestion : public CFiledQuestion
    {
    public:
        virtual QuestionType Type(void) const { return typeOffice; }
    };

    class COfficeCriterion : public CPropertyCriterion
    {
    public:
        virtual QuestionType Type(void) const { return typeOffice; }
    };

    class COfficeAnswer : public CPropertyAnswer
    {
    public:
        virtual QuestionType Type(void) const { return typeOffice; }
        std::string answerFile;
    };

    //////////////////////////////////////////////////////////////////////////
    // coding question
    // 
    class CCodingQuestion : public CFiledQuestion
    {
    public:
        virtual QuestionType Type(void) const { return typeCoding; }
    };

    class CCodingCriterion : public CFiledCriterion
    {
    public:
        virtual QuestionType Type(void) const { return typeCoding; }
    };

    class CCodingAnswer : public CFiledAnswer
    {
    public:
        virtual QuestionType Type(void) const { return typeCoding; }

        std::string answerChecker;
        virtual void Score(CCriterion *criterion);

    protected:
        void PrepareScoring(CCodingCriterion *criterion);
    };

    //////////////////////////////////////////////////////////////////////////
    // html question
    // 
    class CHtmlQuestion : public CFiledQuestion
    {
    public:
        virtual QuestionType Type(void) const { return typeHtml; }
    };

    class CHtmlCriterion : public CPropertyCriterion
    {
    public:
        virtual QuestionType Type(void) const { return typeHtml; }
    };

    class CHtmlAnswer : public CPropertyAnswer
    {
    public:
        virtual QuestionType Type(void) const { return typeHtml; }
    };

    //////////////////////////////////////////////////////////////////////////
    // photoshop question
    // 
    class CPhotoshopQuestion : public CFiledQuestion
    {
    public:
        virtual QuestionType Type(void) const { return typePhotoshop; }
    };

    class CPhotoshopCriterion : public CCriterion
    {
    public:
        virtual QuestionType Type(void) const { return typePhotoshop; }
    };

    class CPhotoshopAnswer : public CFiledAnswer
    {
    public:
        virtual QuestionType Type(void) const { return typePhotoshop; }
    };

    //////////////////////////////////////////////////////////////////////////
    // general creators
    inline SharedPtr<CQuestion> QuestionCreator(QuestionType type)
    {
        switch (type)
        {
        case typeChoice:
            return new CChoiceQuestion;
        case typeFilling:
            return new CFillingQuestion;
        case typeTyping:
            return new CTypingQuestion;
        case typeQnA:
            return new CQNAQuestion;
        case typeOffice:
            return new COfficeQuestion;
        case typeCoding:
            return new CCodingQuestion;
        case typeHtml:
            return new CHtmlQuestion;
        case typePhotoshop:
            return new CPhotoshopQuestion;
        default:
            return NULL;
        }
    }

    inline SharedPtr<CCriterion> CriterionCreator(QuestionType type)
    {
        switch (type)
        {
        case typeChoice:
            return new CChoiceCriterion;
        case typeFilling:
            return new CFillingCriterion;
        case typeTyping:
            return new CTypingCriterion;
        case typeQnA:
            return new CQNACriterion;
        case typeOffice:
            return new COfficeCriterion;
        case typeCoding:
            return new CCodingCriterion;
        case typeHtml:
            return new CHtmlCriterion;
        case typePhotoshop:
            return new CPhotoshopCriterion;
        default:
            return NULL;
        }
    }

    inline SharedPtr<CAnswer> AnswerCreator(QuestionType type)
    {
        switch (type)
        {
        case typeChoice:
            return new CChoiceAnswer;
        case typeFilling:
            return new CFillingAnswer;
        case typeTyping:
            return new CTypingAnswer;
        case typeQnA:
            return new CQNAAnswer;
        case typeOffice:
            return new COfficeAnswer;
        case typeCoding:
            return new CCodingAnswer;
        case typeHtml:
            return new CHtmlAnswer;
        case typePhotoshop:
            return new CPhotoshopAnswer;
        default:
            return NULL;
        }
    }

}

#endif